﻿/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking;

namespace DiningPhilosophers
{


    public class Payment
    {
        public int Id { get; set; }

        public int Servings { get; set; }

        public int EatTime { get; set; }

        public int SleepTime { get; set; }

    }


    public class TableBase : Protected
    {

        private int _diners;
        private int _missingPayments;
        private int _eatingCount;
        private int _pairCount;
        private int _singletonCount;
        private Payment[] _payments;


        protected int Requeues { get; set; }


        public virtual int Diners
        {
            get { return _diners; }
            set
            {
                _diners = value;
                _payments = new Payment[_diners + 1];
                _missingPayments = _diners;
            }
        }


        public int Servings { get; set; }


        [ReadOnlyOperation]
        public Func<bool> GetBill { get; private set; }


        [ReadWriteOperation]
        public Action<int, int, int, int> Pay { get; private set; }


        [Entry]
        public Func<Payment[]> GetBalance { get; private set; }


        [ReadOnlyOperation]
        public Func<int> RequeuesCount { get; private set; }


        [ReadOnlyOperation]
        public Func<int> PairCount { get; private set; }


        [ReadOnlyOperation]
        public Func<int> SingletonCount { get; private set; }


        [Entry]
        public FuncFamily<int, bool> Get { get; private set; }


        [Entry]
        public FuncFamily<int, bool> Wait { get; private set; }


        [ReadWriteOperation]
        public Action<int> Put { get; private set; }


        protected int Right(int fork)
        {
            return fork < Diners ? fork + 1 : 1;
        }


        protected void StartEating()
        {
            _eatingCount += 1;
            if (_eatingCount == 1)
                _singletonCount += 1;
            else
                _pairCount += 1;
        }


        protected void StopEating()
        {
            _eatingCount -= 1;
        }


        public TableBase()
        {
            ReadOnlyOperation(GetBill, Is: () =>
            {
                return _singletonCount + _pairCount >= Servings;
            });


            ReadWriteOperation(Pay, Is: (id, eatCount, eatTime, sleepTime) =>
            {
                _payments[id] = new Payment { Id = id, Servings = eatCount, EatTime = eatTime, SleepTime = sleepTime };
                _missingPayments -= 1;
            });


            Entry(GetBalance,
                When: () => _singletonCount + _pairCount >= Servings && _missingPayments == 0,
            Is: () =>
            {
                return _payments;
            });


            ReadOnlyOperation(RequeuesCount, Is: () =>
            {
                return Requeues;
            });


            ReadOnlyOperation(SingletonCount, Is: () =>
            {
                return _singletonCount;
            });


            ReadOnlyOperation(PairCount, Is: () =>
            {
                return _pairCount;
            });
        }

    }


    public class Table1 : TableBase
    {

        private bool[] _available;
        private int[] _reserved;
        protected int[] _heldForks;


        public override int Diners
        {
            get { return base.Diners; }
            set
            {
                base.Diners = value;
                _available = new bool[Diners + 1];
                _reserved = new int[Diners + 1];
                _heldForks = new int[Diners + 1];

                for (int i = 1; i <= Diners; i++)
                {
                    _available[i] = true;
                    _reserved[i] = 0;
                    _heldForks[i] = 0;
                }
            }
        }


        protected void TryGet(int id, int fork)
        {
            if (_available[fork])
            {
                _heldForks[id] += 1;
                _available[fork] = false;
            }
            else
                _reserved[fork] = id;
        }


        private void DoPut(int fork)
        {
            if (_reserved[fork] != 0)
            {
                _heldForks[_reserved[fork]] += 1;
                _reserved[fork] = 0;
            }
            else
                _available[fork] = true;
        }


        public Table1()
        {
            Entry(Get,
                When: () => true,
            Is: id =>
            {
                TryGet(id, id);
                TryGet(id, Right(id));
                if (_heldForks[id] < 2)
                    Requeue(Wait[id]);
                else
                    StartEating();
                return true;
            });


            ReadWriteOperation(Put, Is: id =>
            {
                _heldForks[id] = 0;
                DoPut(id);
                DoPut(Right(id));
                StopEating();
            });


            Entry(Wait,
                When: id => _heldForks[id] == 2,
            Is: id =>
            {
                Requeues += 1;
                StartEating();
                return true;
            });
        }

    }


    public class Table2 : TableBase
    {

        private bool[] _available;
    
    
        [Entry]
        public FuncFamily<int, bool> Get2 { get; private set; }


        public override int Diners
        {
            get { return base.Diners; }
            set
            {
                base.Diners = value;
                _available = new bool[Diners + 1];

                for (int i = 1; i <= Diners; i++)
                {
                    _available[i] = true;
                }
            }
        }

    
        public Table2()
        {
            Entry(Get,
                When: id => _available[id] || _available[Right(id)],
            Is: id =>
            {
                if (_available[id] && _available[Right(id)])
                {
                    _available[id] = false;
                    _available[Right(id)] = false;
                    return true;
                }
                if (_available[id])
                {
                    _available[id] = false;
                    Requeue(Get2[Right(id)]);
                }
                else
                {
                    _available[Right(id)] = false;
                    Requeue(Get2[id]);
                }
                return false;
            });


            Entry(Wait,
                When: () => false,
            Is: id =>
            {
                return true;
            });


            Entry(Get2,
                When: id => _available[id],
            Is: id =>
            {
                Requeues += 1;
                _available[id] = false;
                StartEating();
                return true;
            });


            ReadWriteOperation(Put, Is: id =>
            {
                _available[id] = true;
                _available[Right(id)] = true;
                StopEating();
            });
        }
    
    }


    public class Table3 : Table1
    {

        public Table3()
        {
            Entry(Get,
                //When: id => _available[id] || _available[Right(id)],
                When: () => true,
            Is: id =>
            {
                TryGet(id, id);
                TryGet(id, Right(id));
                if (_heldForks[id] == 2)
                    StartEating();
                return _heldForks[id] == 2;
            });
        }

    }


    public class Table4 : TableBase
    {

        private bool[] _available;


        public override int Diners
        {
            get { return base.Diners; }
            set
            {
                base.Diners = value;
                _available = new bool[Diners + 1];

                for (int i = 1; i <= Diners; i++)
                {
                    _available[i] = true;
                }
            }
        }

    
        public Table4()
        {
            Entry(Get,
                When: id => _available[id] && _available[Right(id)],
            Is: id =>
            {
                _available[id] = false;
                _available[Right(id)] = false;
                StartEating();
                return true;
            });


            Entry(Wait,
                When: () => false,
            Is: id =>
            {
                return true;
            });


            ReadWriteOperation(Put, Is: id =>
            {
                _available[id] = true;
                _available[Right(id)] = true;
                StopEating();
            });
        }

    }

}
